<template>
  <div
    class="virtual-table-wrapper"
    :style="{ width: typeof width === 'number' ? width + 'px' : width, height: height + 'px' }"
    ref="wrapperRef"
  >
    <div class="virtual-table-header" :style="{ width: totalTableWidth + 'px' }">
      <div
        v-for="(col, colIdx) in columns"
        :key="col.key"
        class="virtual-table-th"
        :class="{ 'is-fixed': colIdx < fixedLeftCount }"
        :style="getColStyle(col, colIdx)"
      >
        <el-tooltip v-if="col.tooltip" :content="col.tooltip" placement="top">
          <span>{{ col.title }}</span>
        </el-tooltip>
        <span v-else>{{ col.title }}</span>
      </div>
    </div>
    <div
      class="virtual-table-body"
      :style="{ height: bodyHeight + 'px', width: totalTableWidth + 'px' }"
      @scroll="onScroll"
      ref="bodyRef"
    >
      <div :style="{ height: totalHeight + 'px', position: 'relative' }">
        <div
          v-for="rowIdx in visibleIndexes"
          :key="rowIdx"
          class="virtual-table-row"
          :style="getRowStyle(rowIdx)"
        >
          <div
            v-for="(col, colIdx) in columns"
            :key="col.key"
            class="virtual-table-td"
            :class="{ 'is-fixed': colIdx < fixedLeftCount }"
            :style="getColStyle(col, colIdx)"
          >
            <span>{{ data[rowIdx][col.key] }}</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from "vue";
import { ElTooltip } from "element-plus";
import type { CSSProperties } from "vue";

interface Column {
  key: string;
  title: string;
  width?: number;
  tooltip?: string;
  fixed?: boolean;
}

const props = defineProps<{
  columns: Column[];
  data: Record<string, any>[];
  rowHeight: number;
  height: number;
  width: number | string;
  fixedLeftCount?: number;
}>();

const wrapperRef = ref<HTMLElement | null>(null);
const bodyRef = ref<HTMLElement | null>(null);
const fixedLeftCount = computed(() => props.fixedLeftCount ?? 0);

const totalTableWidth = computed(() =>
  props.columns.reduce((sum: number, col) => sum + (col.width || 120), 0)
);
const bodyHeight = computed(() => props.height - 36); // header 36px
const totalHeight = computed(() => props.data.length * props.rowHeight);

// 虚拟滚动核心
const visibleCount = computed(() => Math.ceil(bodyHeight.value / props.rowHeight) + 4);
const startIdx = ref(0);
const endIdx = computed(() => Math.min(startIdx.value + visibleCount.value, props.data.length));
const visibleIndexes = computed(() => {
  const arr: number[] = [];
  for (let i = startIdx.value; i < endIdx.value; i++) arr.push(i);
  return arr;
});

function onScroll(e: Event) {
  const target = e.target as HTMLElement;
  const scrollTop = target.scrollTop;
  startIdx.value = Math.max(0, Math.floor(scrollTop / props.rowHeight) - 2);
}

function getRowStyle(rowIdx: number): CSSProperties {
  return {
    position: "absolute" as const,
    top: rowIdx * props.rowHeight + "px",
    height: props.rowHeight + "px",
    width: "100%",
    display: "flex",
    background: rowIdx % 2 === 0 ? "#fff" : "#fafbfc",
  };
}

function getColStyle(col: Column, colIdx: number): CSSProperties {
  const style: CSSProperties = {
    width: (col.width || 120) + "px",
    minWidth: (col.width || 120) + "px",
    maxWidth: (col.width || 120) + "px",
    boxSizing: "border-box",
    flex: `0 0 ${col.width || 120}px`,
    overflow: "hidden",
    textOverflow: "ellipsis",
    whiteSpace: "nowrap",
  };
  if (colIdx < fixedLeftCount.value) {
    style.position = "sticky";
    style.left =
      Array.from({ length: colIdx }).reduce(
        (sum: number, _, i) => sum + (props.columns[i].width || 120),
        0
      ) + "px";
    style.zIndex = 2;
    style.background = "#f6f8fa";
    style.borderRight = "1px solid #eee";
  }
  return style;
}

// 滚动到顶部时重置
watch(
  () => props.data.length,
  () => {
    startIdx.value = 0;
    nextTick(() => {
      if (bodyRef.value) bodyRef.value.scrollTop = 0;
    });
  }
);

onMounted(() => {
  if (bodyRef.value) bodyRef.value.scrollTop = 0;
});
</script>

<style scoped lang="scss">
.virtual-table-wrapper {
  position: relative;
  overflow: auto;
  background: #fff;
  border: 1px solid #ebeef5;
  border-radius: 4px;
}
.virtual-table-header {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  height: 36px;
  background: #f6f8fa;
  border-bottom: 1px solid #ebeef5;
}
.virtual-table-th {
  position: relative;
  display: flex;
  align-items: center;
  height: 36px;
  padding: 0 8px;
  font-size: 14px;
  font-weight: 500;
  line-height: 36px;
  white-space: nowrap;
  background: #f6f8fa;
  border-right: 1px solid #ebeef5;
}
.virtual-table-th:last-child {
  border-right: none;
}
.virtual-table-body {
  position: relative;
  overflow: auto;
}
.virtual-table-row {
  display: flex;
  align-items: center;
  font-size: 13px;
  border-bottom: 1px solid #f0f0f0;
}
.virtual-table-td {
  position: relative;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 8px;
  white-space: nowrap;
  background: inherit;
  border-right: 1px solid #f0f0f0;
}
.virtual-table-td:last-child {
  border-right: none;
}
.is-fixed {
  z-index: 3;
  background: #f6f8fa;
}
</style>
